<?php
/**
 * 这个类的主要功能是分析HTML模型，用选择器的方式获取HTML
 * 可以直接像JQUERY一样进行选择
 * @author LiuYong
 * @email jedece@qq.com
 * @link http://www.caijiche.com完全免费的采集工具
 */

//mb_internal_encoding("UTF-8");

define('SPACE',' ');//空格常量


define("TAG_START",1);//读取文本内容
define("TAG_NAME",2);
define("CLOSE_TAG_NAME",3);
define("ATTR_NAME",4);//读取属性
define("ATTR_VAL",5);//读取属性值
define("TAG_END",6);//标签的结束，遇到符号“>”
define("TAG_OVER",7);//标签的结束，遇到符号“>”,并且是结束标签的时候
define("COMMON_TEXT", 8);


define("NO_USE",9);//没用的字符
define("EQUAL_SYMBOL",10);//等于号
define("VAL_LEFT",11);//读取属性值左边引号
define("VAL_RIGHT",12);//读取属性值左边引号

define("READING_COMMON_TEXT", 1);
define("READING_TAG", 2);
define("READING_TAG_NAME",3);//在标签里面
define("READING_TAG_ATTR_NAME", 4);
define("READING_CLOSE_TAG", 5);//读取关闭标签

define("READING_TAG_ATTR_VAL_START", 6);//准备开始读取属性值
define("READING_TAG_ATTR_VAL", 7);//正在读取属性值.

define("ROOT_NODE","document");

class HtmlDom{

	//public $nodes = array();//结点树，结点数组

	public $doc;

	private $root;

	public $size=0;//html文件的大小


	private $pos=0;//初始游标

	//private $cursor;//活动游标

	private $char;//当前字符

	private $charInfo;

	/**
	 * 0:没有在任何标签控制范围内 READ_HTML
	 * 1：进入一个标签的开始 READ_TAG
	 * 2：成功获取标签名后
	 * 3：成功获取标签的属性
	 * 4：成功获取一个标签<div id="abc">开始获取标签内容
	 * 5：成功获取标签的内容
	 * 6：成功进入一个结束标签内容
	 * 7：成功获取结束标签名
	 * 8：获取结束标签完成
	 */
	private $status=READING_COMMON_TEXT;//分析所处的状态,依据当前字符的语义分析得到

	private $tmpNodes=array();//临时的结点，没有结束的结点标识
	private $nodeCount = 0;//结点计数器

	private $attrValSep = NULL;	//记录一个属性值的分隔符，是双引号还是单引号或什么都没有？
	//	private $nodePos = NULL;//结点游标


	private $singleLineNode = array("!doctype","meta","link","br","input","img","br\\","wbr");//这里定义没有或不可能有子节点的元素，这些在处理的时候，不会做为任何元素的父节点。
	private $noChildNode = array("style","textarea","script");
	private $ignoreNode = array("script","noscript");//在解析DOM树时，忽略的结点
	private $listNode = array("li");//列表的结点，这个结点不可以包含同类的自身结点
	//private $escapeFlag = False;//转义标识，在解析HTML的时候，遇到一个“\"”这样的字符，我们认为是转义成了一个引号的值
	
	public static function init($html){
		$dom = new HtmlDom($html);
		return $dom->root;//返回它的根结点
	}
	


	/**
	 * 构造函数
	 * @param String $str
	 */
	function __construct($html=NULL){
		if(!is_null($html)){

			$this->doc = $this->removeNoise($html);
			//去除HTML中没用的信息
			$this->size = strlen($this->doc);// 文件的长度

			//log::debug($this->doc);

			
			if($this->size>0){
				$rootNode = new HtmlDomNode($this);
				$rootNode ->startPos = 0;
				$rootNode ->endPos = $this->size - 1;
				$rootNode ->tag = ROOT_NODE;
				$rootNode ->parent = NULL;
				$rootNode ->nodeId = ++$this->nodeCount;
				//$rootNode ->nodeSN = "N_".$this->nodeCount;
				$this->tmpNodes[] = $rootNode;
				$this->initDom();
				$this->root = $rootNode;
				//var_dump($this->nodes);
				//log::debug("have children = ".count($this->root->children));
			}else{
				//当传入的是一个空串时，也要返回一个空对象
				$rootNode = new HtmlDomNode($this);
				$rootNode ->startPos = 0;
				$rootNode ->endPos = 0;
				//$rootNode ->tag = ROOT_NODE;
				$rootNode ->parent = NULL;
				$this->root = $rootNode;
			}
		}
	}


	/**
	 * 将HTML的没用的信息清除
	 * @这里是否影响性能有待研究
	 */
	private function removeNoise($str){
		$str = preg_replace("'<#--.*?-->'is", "", $str);//去除注释等信息
		$str = preg_replace("'<\!--.*?-->'is", "", $str);//去除注释等信息
		$str = preg_replace("'<\!.+?>'is", "", $str);//去除注释等信息
		$str = preg_replace("'<script.+?</script>'is", "", $str);
		$str = preg_replace("'<style.+?</style>'is", "", $str);
		$str = preg_replace("'<br\\\\>'is", "<br/>", $str);
		$str = preg_replace("'\r\n'is", "", $str);
		return $str;
	}


	/**
	 * 获取所有的子结点
	 * 分析的主要入口函数
	 * 分析得到所有的结点集合
	 */
	private function initDom(){
		//遍历所有的数据一个字符一个字符分析

		$tmpStr=NULL;//临时缓存字符串
		$endTag = NULL;//临时变更，存储最后找到的一个结束标签的内容
		$lastTagEndPos = NULL;//上一个“>”开始的地方
		$lastTagStartPos  = NULL;//上一个“<”开始的地方
		for(;$this->pos<$this->size;$this->pos++){//遍历文件中的每一个文字，逐字分析

			$this->char = $this->doc[$this->pos];

			$oldCharInfo = $this->charInfo;//记录上一个字符的意义

			$this->charInfo = $this->getCharInfo();//读取当前字符的意义

			//log::debug("({$this->pos}){$this->char} = $this->charInfo");

			if($oldCharInfo === $this->charInfo){
				$tmpStr .= $this->char;
			}else{
				//将组成的信息保存好
				//发现一个新的结点
				if($oldCharInfo == TAG_START){
					foreach ($this->tmpNodes as $node){
						if(!in_array($node->tag,$this->singleLineNode,true)){
							$node->textEnd = $this->pos-2;
						}
					}
					$lastTagStartPos = $this->pos - 1;
				}elseif($oldCharInfo == TAG_NAME){//标签类型
					$tmpStr = strtolower(trim($tmpStr));
					$lastNode = end($this->tmpNodes);
					//$lastKey = key($this->tmpNodes);//上一个结点的信息，父节点肯定没有封闭完成


					//自动封闭上一步没有封闭的一些自动封闭节点
					//$lastNode = $this->tmpNodes[$lastKey];
					if(in_array(strtolower($lastNode->tag),$this->singleLineNode,true)){//如果上一个结点是一个需要自动封闭的结点
						$lastNode->endPos = $lastTagEndPos;
						array_pop($this->tmpNodes);//将最后一个元素弹出
						$lastNode = end($this->tmpNodes);
					}elseif(in_array(strtolower($lastNode->tag),$this->listNode,true) && $tmpStr == $lastNode->tag){//如果是列表元素，做特殊处理
						$lastNode->endPos = $lastTagStartPos - 1;
						//$lastNode->textEnd = $lastTagEndPos;
						array_pop($this->tmpNodes);//将最后一个元素弹出
						$lastNode = end($this->tmpNodes);
					}

					//封闭上一个结点结束

					$newNode = new HtmlDomNode($this);//初始化一个结点
					$newNode->startPos = $lastTagStartPos;//减去标签占有的长度
					$newNode->tag = $tmpStr;//转小写后保存

					$newNode->parent = $lastNode;//指定结点的父结点
					$newNode->index = count($lastNode->children);//在兄弟中的排行
					$newNode->nodeId = ++$this->nodeCount;
					$this->tmpNodes[] = $newNode;//将新结点压入栈,返回新数组的长度

					if(!in_array($tmpStr, $this->ignoreNode)){
						$len = array_push($lastNode->children, $newNode);//将子结点压入栈
						$newNode->brotherIndex = $len-1;
					}


				}elseif($oldCharInfo === TAG_OVER){//如果这是一个标签的结束字符
					$lastTagEndPos = $this->pos - 1;
					while(1){
						$lastNode = end($this->tmpNodes);
						//最后一个元素
						if($lastNode->tag==ROOT_NODE)break;//如果这个里面只有ROOT这个元素，照样要跳出

						if($endTag === $lastNode->tag ){//如果找到的这个节点和堆栈中最后一个元素的值匹配
							$lastNode->endPos = $this->pos - 1;
							array_pop($this->tmpNodes);//将最后一个元素弹出
							$endTag = NULL;
							break;//跳出循环
						}elseif(in_array($lastNode->tag,$this->singleLineNode,true)){//自动封闭结点，不进行匹配一切自动完成
							$lastNode->endPos = $this->pos - 1;
							array_pop($this->tmpNodes);//将最后一个元素弹出
							//break;//如果这是一些没有子结点的标签，则一样跳出
						}else{
							//标签不平衡的情况，也许是有单标签存在，也许就是找到的这个结束标签和目前数组中最后 一个不匹配
							//是不是应该放弃这个不平衡的结束标签？
							//查找暂存数组里有没有这个标签的前置项目，如果有，则封闭它，并将它的子结点全部强制封闭
							//向上找，查看是否有这个结点存在
							//$n = $lastKey;
							$lastNode = end($this->tmpNodes);
							while(1){
								$par = $lastNode->parent;
								if(is_null($par) || $par->tag == ROOT_NODE)break 2;//如果没有找到父节点，直接退出两层循环
								if($endTag == $par->tag)break;
								$lastNode = $par;
							}
							$lastNode = end($this->tmpNodes);
							//弹出这个节点
							$lastNode->endPos = $this->pos - 1;
							//$this->nodes[] = $lastNode;
							array_pop($this->tmpNodes);//将最后一个元素弹出
							//break;//暂时决定放弃这个标签
						}
					}
				}elseif($oldCharInfo === ATTR_NAME){//如果这是一个属性字符串
					$tmpAttrName = strtolower(trim($tmpStr));//属性
				}elseif($oldCharInfo === ATTR_VAL){//如果这是一个属性的值
					//end($this->tmpNodes);
					$lastNode = end($this->tmpNodes);
					//$lastKey = key($this->tmpNodes);
					$lastNode->attr[$tmpAttrName] = trim($tmpStr);//属性值不能变成小写，因为有些主机是大小写第三的，这样会导致类似于URL之类的属性值失效
				}elseif($oldCharInfo === TAG_END){//如果这是一个属性的值,有些单标签可能在这个地方结束，记录下来
					$lastTagEndPos = $this->pos - 1;
					$lastNode = end($this->tmpNodes);
					//end($this->tmpNodes);
					//$lastKey = key($this->tmpNodes);
					$lastNode->endPos = $this->pos - 1;

					$_tag = $lastNode->tag;


					if(in_array(strtolower($_tag),$this->singleLineNode,true)){//如果上一个结点是一个需要自动封闭的结点
						$this->nodes[] = $lastNode;
						array_pop($this->tmpNodes);//将最后一个元素弹出
						//end($this->tmpNodes);
						//$lastKey = key($this->tmpNodes);
						$lastNode = end($this->tmpNodes);
					}else{
						$lastNode->textStart = $this->pos;//记录文字开始的地方
					}

					if(in_array(strtolower($_tag),$this->noChildNode,true)){
						//log::debug("_tag = $_tag({$this->pos})");
						//直接跳转到这个结束标签的附近；
						$pos = strpos($this->doc, "</$_tag>",$this->pos);
						if($pos !== false){
							$this->pos = $pos;
						}else{
							$this->pos = $this->size;
						}
						//log::debug("this->pos = {$this->pos}");
						continue;
					}
				}elseif($oldCharInfo === CLOSE_TAG_NAME){//是一个结束的标签名称
					$endTag = strtolower(trim($tmpStr));
				}

				$tmpStr = $this->char;
			}

			//将ROOT标签收尾
			if($this->pos == $this->size - 1){//游标到了最后，将根结点完结
				//log::debug("tmpNode:".var_export($this->tmpNodes,true));
				while(count($this->tmpNodes)>0){
					//end($this->tmpNodes);
					//$lastKey = key($this->tmpNodes);
					$lastNode = end($this->tmpNodes);
					$lastNode->endPos = $this->size;
					//$this->nodes[] = $lastNode;
					//$this->nodes[]->endPos = $this->size;
					array_pop($this->tmpNodes);
				}
				//$this->tmpNodes = NULL;//将临时数组置空，释放内存
			}else{
				//如果发现一些常规文本，则直接查找下一个“<”,跳过中间的检索
				if($this->charInfo == COMMON_TEXT){
					$np = strpos($this->doc, "<",$this->pos);
					if($np !==false){
						$this->pos = $np - 1;
					}
				}
			}
			//Log::debug("char = {$this->char}({$this->pos}) charInfo={$this->charInfo} status={$this->status} posLocation={$this->posLocation}");
		}
	}
	//获取当前字符的意义
	private function getCharInfo(){

		//正在读普通文本时，除非遇到左尖括号，否则永远认为它是一个普通文本
		if($this->status == READING_COMMON_TEXT){
			if($this->char==="<"){
				$this->status = READING_TAG_NAME;//直接进入读取标签名称的状态
				return TAG_START;
			}else{
				return COMMON_TEXT;
			}
		}

		//在读取名称的时候，除非遇到空格，否则永远认为是一个名称的一部分
		if($this->status == READING_TAG_NAME){
			if($this->char===SPACE){
				$this->status = READING_TAG_ATTR_NAME;//开始读取读值
				return NO_USE;
			}elseif($this->char===">"){//遇到一个结束符
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}elseif($this->char==="/"){//遇到一个反斜杠
				$this->status = READING_CLOSE_TAG;//读取常规文本
				return NO_USE;
			}else{
				return TAG_NAME;
			}
		}

		if($this->status == READING_TAG_ATTR_NAME){
			if($this->char==="="){//遇到等于号
				$this->status = READING_TAG_ATTR_VAL_START;//开始读取读值
				return EQUAL_SYMBOL;
			}elseif($this->char===">"){//遇到一个结束符
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}elseif($this->char==="/"){//遇到一个反斜杠
				$this->status = READING_CLOSE_TAG;//读取常规文本
				return NO_USE;
			}else{
				return ATTR_NAME;
			}
		}

		//开始读取属性值前面的引号
		if($this->status == READING_TAG_ATTR_VAL_START){
			if($this->char===SPACE){
				return NO_USE;
			}else{
				$this->status = READING_TAG_ATTR_VAL;
				if($this->char==="\"" || $this->char==="'"){
					$this->attrValSep = $this->char;//引号记录下来
					return VAL_LEFT;
				}else{
					return ATTR_VAL;
				}
			}
		}

		//开始读取属性值
		if($this->status == READING_TAG_ATTR_VAL){//正在读标签值的时候
			if($this->char===$this->attrValSep){
				//如果在读取标签的时候，遇到了引号（前面使用的那个字符），则属性读取完成
				$this->status = READING_TAG_ATTR_NAME;//继续进入找标签名称的状态
				$this->attrValSep = NULL;
				return VAL_RIGHT;
			}elseif($this->char===SPACE && $this->attrValSep==NULL){
				//如果在读取标签的时候，遇到了引号（前面使用的那个字符），则属性读取完成
				$this->status = READING_TAG_ATTR_NAME;//继续进入找标签名称的状态
				return NO_USE;
			}elseif($this->char===">" && $this->attrValSep==NULL){
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}else{
				//如果在读取标签属性值没有结束时，遇到任何信息都认为是值的一部分
				return ATTR_VAL;
			}
		}


		//读取结束标签状态
		if($this->status == READING_CLOSE_TAG){
			if($this->char===">"){
				$this->status = READING_COMMON_TEXT;//开始读取常规文本
				return TAG_OVER;
			}else{
				return CLOSE_TAG_NAME;
			}
		}
	}



	/**
	 * 析构函数
	 * 释放内存
	 */
	function __destruct() {
		$this->doc = null;
	}

	/**
	 * 转成字符串
	 */
	function __toString(){
		return $this->doc;
	}
}

/**
 *
 * @author LiuYong
 * html的结点类
 */
class HtmlDomNode{

	public $tag = NULL;//标签类型，比如DIV,A,TEXT,FONT
	public $nodeId = 0;//结点的编号，预计方便与前台操作协调用
	public $index = 0;//这个节点在同辈是的排行
	public $startPos;
	public $endPos;
	public $parent;
	public $textStart = NULL;//当标识文字开始与结束的地方是空时，说明没有这个内容存在
	public $textEnd = NULL;
	public $attr=array();//属性
	public $children=array();//子结点们
	public $dom=NULL;//DOM模型传进来，目前主要就是用做引用HTML原文件

	/**
	 * 构造函数
	 * @param HtmlDom $dom
	 */
	function __construct($dom = NULL){
		$this->dom = $dom;
	}

	/**
	 * 析构函数
	 */
	function __destruct(){
		unset($this->doc);
	}

	/**
	 * 直接转换成字符串的函数
	 */
	function __toString(){
		//return $this->html();
		return $this->makeup();
		//return $this->innerText();
	}

	/**
	 * 是否有子节点
	 */
	public function hasChildren(){
		return !empty($this->children);
	}
	
	/**
	 * 获取所有的属性值
	 */
	function getAllAttributes() {return $this->attr;}
	
	
	function getElementById($id) {return $this->find("#$id");}
	function getElementsById($id) {return $this->find("#$id");}
	//function getElementByTagName($name) {return $this->find($name);}
	function getElementsByTagName($name) {return $this->find($name);}

	/**
	 * 返回此元素的第一个孩子节点
	 */
	public function firstChild(){
		return count($this->children)>0?reset($this->children):NULL;
	}
	
	/**
	 * 返回最后一个孩子节点
	 */
	public function lastChild(){
		return count($this->children)>0?end($this->children):NULL;
	}
	
	/**
	 * 重新生成HTML
	 */
	public function makeup($incAttr = TRUE){
		$singleLineNode = array("meta","link","br","input","img","br\\","wbr");
		
		if(in_array($this->tag, $singleLineNode)){
			$html = "<{$this->tag}";
			if($incAttr)foreach ($this->attr as $key=>$val)$html .= " $key=\"$val\"";

			$html .= "/>";
			
			//return $html;//单结点可能是没有文字内容的
		}else{
			$html = "<{$this->tag}";
			if($incAttr)foreach ($this->attr as $key=>$val)$html .= " $key=\"$val\"";
			$html .= ">";
			$textPos = $this->textStart;
			foreach ($this->children as $node){
				//中间文字
				if($textPos<$node->startPos-1){
					$html .= substr($this->dom->doc,$textPos,$node->startPos - $textPos);
				}
				//子结点文字
				$html .= $node->makeup($incAttr);
				$textPos = $node->endPos+1;
			}
			
			//后置文字
			if($textPos<=$this->textEnd){
				$html .= substr($this->dom->doc,$textPos,$this->textEnd - $textPos+1);
			}
			
			$html .= "</{$this->tag}>";
		}
		return $html;
	}
	
	/**
	 * 下一个兄弟结点
	 */
	public function nextSibling(){
		if(count($this->parent->children)-1>$this->index)return $this->parent->children[$this->index+1];
		return NULL;
	}
	
	/**
	 * 查找一个元素，返回一个节点对象htmlDomNode
	 * @param Array $selector//这是一个二维数组
	 * @param String $startPos
	 * @param Int $maxDepth 查找的最大深度
	 */
	private function seek($selector){

		//$cursor = $cursor==NULL?$this->root:$cursor;

		$result = array();//这是一个结果数组
		//if(--$maxDepth<0)return $result;

		$len = count($selector);//选择器的层次数
		if(!is_array($selector) || $len == 0)return $result;//如果选择器没有内容后
		//$parent = $cursor;
		//$children = array_push(array($this), $this->children);//要包括它自己在内
		$children = $this->children;

		//选择器的过虑对象
		$tag = $selector[0][0];
		$key = $selector[0][1];
		$val = $selector[0][2]==NULL?NULL:strtolower($selector[0][2]);
		$exp = $selector[0][3];
		$cl = $selector[0][4];
		$clval = $selector[0][5];
		$child = $selector[0][6];//如果是大于号，则只查找本结点，如果是空格，则可以在所有子节点，孙子节点里查找
		$no_key = $selector[0][7];


			
		for($i=0;$i<count($children);$i++){
			//$node = $children[$i];
			$currentNode = $children[$i];//遍历的当前节点

			$tagName = $currentNode->tag;


			//判断这个结点是否符合当前的选择器
			$match = TRUE;//默认是匹配

			if(!isset($tag))return array();//如果没有发现这个TAG，则可能是选择器出错了

			if($tag!=="*" && $tag != $tagName){//如果设置了标签
				$match=FALSE;
			}

			//var_dump($exp);
			if(isset($key)){
				if(!isset($currentNode->attr[$key])){
					//如果发现这个标签没有这个属性，则返回不匹配
					$match=FALSE;
				}else{
					//如果已经设置了属性的值，则要匹配这个属性的值
					$attVal = strtolower($currentNode->attr[$key]);//属性的值
					if(isset($val)){
						//这里要对匹配的几种情况进行处理
						//对表达式的操作符进行处理
						switch ($exp){
							case "=":
								$atts = explode(" ",$attVal);
								if((!is_array($atts) || !in_array($val, $atts)) && $attVal!=$val)$match=FALSE;
								//if($attVal!=$val)$match=FALSE;
								break;
							case "|="://属性包选择器 选择指定属性等于给定字符串或以该字符串为前缀（该字符串后跟一个连字符"-" ）的元素。
								if(strpos($attVal, $val."-")===false)$match=FALSE;//没有找到
								break;
							case "*="://选择指定属性具有包含一个给定的子字符串的元素。（选择给定的属性是以包含某些值的元素）
								if(strpos($attVal, $val)===false)$match=FALSE;
								break;
							case "~=":// 属性选择器选择指定属性用空格分隔的值中包含一个给定值的元素。
								$atts = explode(" ",$attVal);
								if(!is_array($atts) || !in_array($val, $atts))$match=FALSE;
								break;
							case "$="://属性选择器选择指定属性是以给定值结尾的元素。这个比较是区分大小写的。{暂时不支持大小写}
								if(strpos($attVal, $val)!==strlen($attVal)-strlen($val))$match=FALSE;
								break;
							case "!="://属性选择器选择不存在指定属性，或者指定的属性值不等于给定值的元素。
								if($attVal==$val)$match=FALSE;
								break;
							case "^="://属性选择器选择指定属性是以给定字符串开始的元素
								if(strpos($attVal, $val)!==0)$match=FALSE;
								break;
							default://不被支持的表达式
								$match=FALSE;
						}
					}
				}
			}

			//开始对部分伪类选择器进行处理
			//log::debug("cl = $cl");
			switch ($cl){
				case "has"://选择含有选择器所匹配的至少一个元素的元素。表达式 $('div:has(p)') 匹配一个 <div>如果<p>在其后代中存在的任何地方，不仅是一种直接的子元素。
					$ns = $currentNode->find($clval);//在子结点中查找
					if(!isset($ns[0]) || count($ns[0])<=0)$match=FALSE;
					break;
				case "eq"://Selector选择一个给定索引值的元素。（从 0 开始计数）
					if($clval != $i)$match=FALSE;
					break;

				case "gt"://:gt() Selector选择所有大于给定索引值的元素
					if($i <= $clval)$match=FALSE;
					break;

				case "first":// Selector选择第一个匹配的元素。
					if(0 != $i)$match=FALSE;
					break;

				case "first-child":// Selector选择第一个匹配的元素。
					if(0 != $i)$match=FALSE;
					break;

				case "even"://:even Selector选择偶数元素，从 0 开始计数。同样查看奇数
					if($i%2 !== 0)$match=FALSE;
					break;

				case "odd"://选择奇数元素，从 0 开始计数。同样查看偶数 特别地注意的是，这是基于0的索引，所以:odd选择器是选择第二个元素，第四个元素，依此类推在匹配。
					if($i%2 == 0)$match=FALSE;
					break;

				case "button":// Selector选择所有按钮元素和类型为按钮的元素。
					if($tagName != "button" && ($tagName != "input" || ($tagName == "input" && $currentNode->attr["type"]!="button")))$match=FALSE;
					break;

				case "checkbox "://Selector选择所有类型为复选框的元素。
					if(!($tagName == "input" && isset($currentNode->attr["type"]) && $currentNode->attr["type"]=="checkbox"))$match=FALSE;

					break;

				case "checked":// Selector选择所有勾选的元素。
					if(!isset($currentNode->attr["checked"]))$match=FALSE;
					break;

				case "disabled"://Selector选择所有被禁用的元素。
					if(!isset($currentNode->attr["disabled"]))$match=FALSE;
					break;

				case "enabled"://Selector选择所有可用的元素。
					if(isset($currentNode->attr["disabled"]))$match=FALSE;
					break;

				case "empty"://Selector选择所有没有子元素的元素（包括文本节点）。
					if(count($currentNode->children)!=0)$match=FALSE;
					break;

				case "file"://:file Selector选择所有类型为文件的元素。
					if(!($tagName == "input" && isset($currentNode->attr["type"]) && $currentNode->attr["type"]=="file"))$match=FALSE;
					break;

				case "header"://Selector选择所有标题元素，像h1, h2, h3 等.
					if($tagName!="h1" & $tagName!="h2" & $tagName!="h3" & $tagName!="h4" & $tagName!="h5" & $tagName!="h6")$match=FALSE;
					break;

				case "image"://Selector选择所有图像类型的元素。"
					if($tagName!="image")$match=FALSE;
					break;

				case "input"://Selector选择所有 input, textarea, select 和 button 元素
					if($tagName!="input" & $tagName!="textarea" & $tagName!="select" & $tagName!="button")$match=FALSE;
					break;
			}

			if($match){
				//泛查找
				if($len-1>=1){//如果还没有解析完成这个结，则还要再去检测
					$newSelector = $selector;
					array_shift($newSelector);//将第一个选择器弹出，因为已经匹配完成
					$childResult = $currentNode->seek($newSelector);//如果选择器没有结束，则继续向下寻找
					if(count($childResult)){//如果子结点里找到了
						$result= array_merge($result, $childResult);
					}
				}else{
					$result[] = $currentNode;//如果这是最后一个结点，则将最终找到的结果返回
				}

			}//else{//如果当前结点不匹配的情况下
//				if($child != ">"){//如果这是直接找子结点的情况，则不能去它的孙子结点继续查找
//					$result_children = $currentNode->seek($selector);//这里进行递归调用 ，在数组里进行查找
//					$result = array_merge($result,$result_children);//合并数组,将子结点的所有结果合并到一起
//				}
//			}
			if($child != ">"){//如果这是直接找子结点的情况，则不能去它的孙子结点继续查找
				$result_children = $currentNode->seek($selector);//这里进行递归调用 ，在数组里进行查找
				$result = array_merge($result,$result_children);//合并数组,将子结点的所有结果合并到一起
			}
		}

		return $result;//返回没有找到的信息
	}
	
	/**
	 * 查询的主要函数
	 * 返回htmlDomNode数组，二维数组
	 * @param String $selector
	 */
	public function find($selector){
		$html = array();//定义返回值
		//if($selector == NULL || strlen($selector)<=0)return NULL;//返回空数据
		$selectors = $this->parseSelector($selector);//将选择器解析成数组

		//var_dump($selectors);

		if (($count=count($selectors))===0) return array();

		for($i=0;$i<count($selectors);$i++){
			//每一个用逗号分开的选择器，多个选择器可以合并在一起
			$p = $this->seek($selectors[$i]);//返回的是一个数组//这是查找一的结点列表
			$html[$i] = $p;
		}

		return $html;//返回HTML信息

	}

	/**
	 * 分析选择器
	 * 将选择器转换成数组
	 */
	private function parseSelector($selector){

		//		$pattern = "/([\w-:\*]*)  (?#匹配一个 标签名 冒号 *号) m11
		//		             (?:\#([\w-]+)|\.([\w-]+))?  (?#这是一个不捕获的匹配 #abc 或 .abcd) m2 m3
		//		             (?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?  (?#这是一个不捕获的匹配  [@!abc='abc']) m4 m5 m6 m5="!= *= ^= $="
		//		             ([\/, ]+)/is";  (?#这是一个不捕获的匹配  /, ) m7

		$pattern = "/([ >]*)?([\w-\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$\|~]?=)[\"']?(.*?)[\"']?)?\])?(?:\:([\w-]+)(?:\((.*)?\))?)?([\/,]*)/is";

		preg_match_all($pattern, trim($selector).' ', $matches, PREG_SET_ORDER);

		//var_dump($matches);

		$selectors = array();
		$result = array();

		foreach ($matches as $m) {
			$m[0] = trim($m[0]);
			if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue;
			// for browser generated xpath
			if ($m[1]==='tbody') continue;

			list($tag, $key, $val, $exp, $cl, $clval,$child, $no_key) = array($m[2], null, null, "=", null, null, null, false);
			if (!empty($m[1])) {$child=$m[1];}
			if (!empty($m[3])) {$key='id'; $val=$m[3];}//第三个元素：属性选择器
			if (!empty($m[4])) {$key='class'; $val=$m[4];}//第四个元素：属性值
			if (!empty($m[5])) {$key=trim($m[5]);}//第五元素：
			if (!empty($m[6])) {$exp=$m[6];}
			if (!empty($m[7])) {$val=$m[7];}
			if (!empty($m[8])) {$cl=$m[8];}
			if (!is_null($m[9])) {$clval=$m[9];}
			//if (!is_null($m[9])) {$child=$m[9];}
			//log::debug("clval:$clval");
			// convert to lowercase
			$tag=empty($tag)?"*":strtolower($tag); //第二个元素：标签名
			$key=is_null($key)?NULL:strtolower($key);
			$cl=is_null($cl)?NULL:strtolower($cl);
			$child=is_null($child)?NULL:trim($child);
			//$key=strtolower($key);
			//elements that do NOT have the specified attribute
			if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;}

			$result[] = array($tag, $key, $val, $exp,$cl,$clval,$child, $no_key);

			//用逗号做分隔符的时候标记一下
			if (trim($m[10])===',') {
				$selectors[] = $result;//将这个结存起来
				$result = array();
			}
		}
		if (count($result)>0)
		$selectors[] = $result;
		/**
		 *           0 => string '*' (length=1)
		 1 => null 属性
		 2 => null 属性值
		 3 => string '=' (length=1) 属性值与属性的关系
		 4 => string 'checked' (length=7) 伪类选择器
		 5 => string '' (length=0)伪类的值
		 6 => string ' ' (length=1) 子结点标识
		 7 => boolean false
		 */
		return $selectors;//分组后的选择器
	}
	
	/**
	 * 上一个兄弟结点
	 */
	public function prevSibling(){
		if($this->index>0)return $this->parent->children[$this->index-1];
		return NULL;
	}
	
	/**
	 * 返回指定编号的孩子结点
	 * @param int $idx
	 */
	function children($idx=-1){
		if($idx==-1)return $this->children;
		if(count($this->children)-1>$idx){
			return $this->children[$idx];
		}
	}

	/**
	 * 获取这个标签的类型名
	 */
	public function nodeName() {return $this->tag;}

	/**
	 * 判断两个对象是不是同一个结点
	 * @return boolean;
	 */
	public function isEqual($obj){
		if(gettype($obj)=="object" && isset($obj->nodeId) && $this->nodeId = $obj->nodeId)return true;
		return false;
	}
	/**
	 * 是否有某个属性
	 */
	public function hasAttribute($name){
		return isset($this->attr[$name]);
	}

	/**
	 * 获取某个属性的值
	 * @param unknown_type $name
	 */
	public function getAttribute($name){
		return $this->hasAttribute($name)?$this->attr[$name]:NULL;
	}

	/**
	 * 获取HTML内容,返回HTML对象
	 */
	public function html(){
		$_html = substr($this->dom->doc,$this->startPos,$this->endPos - $this->startPos + 1);
		return $_html;//将结果保存到返回值当中去
	}

	/**
	 * 获取对象的文本内容,过虑所有的HTML内容
	 * @todo 不知道这种方法是否优于用正则替换，有待验证;
	 */
	public function innerText(){
		if(is_null($this->textStart) || is_null($this->textEnd))return "";//单结点可能是没有文字内容的
		$text = "";//返回的文本内容
		//前置的文字
		$textPos = $this->textStart;
		foreach ($this->children as $node){
			//中间文字
			if($textPos<$node->startPos-1){
				$text .= substr($this->dom->doc,$textPos,$node->startPos - $textPos);
			}
			//子结点文字
			$text .= $node->innerText();
			$textPos = $node->endPos+1;
		}
		//后置文字
		if($textPos<$this->textEnd){
			$text .= substr($this->dom->doc,$textPos,$this->textEnd - $textPos + 1);
		}

		return $text;
	}
}

?>